package com.rainyun.rcmc.bukkit.config;

import com.rainyun.rc.common.VersionException;
import com.rainyun.rc.common.VersionUtil;
import com.rainyun.rc.config.ErrorHandler;
import com.rainyun.rc.config.RainConfig;
import com.rainyun.rc.config.annotation.Desc;
import com.rainyun.rc.config.annotation.IgnoreConf;
import com.rainyun.rc.orm.annotation.SubClass;
import com.rainyun.rc.util.RainF;
import com.rainyun.rc.util.RainStr;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.file.YamlRepresenter;
import org.yaml.snakeyaml.representer.BaseRepresenter;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;

public class BukYmlConfig implements RainConfig {
	
	static List<Class<?>> Representers = new ArrayList<Class<?>>();
	static List<Class<?>> MultiRepresenters = new ArrayList<Class<?>>();
	static boolean loadSuccess;

	/**初始化Bukkit的yml配置核心*/
	static {
		YamlRepresenter yamlp = new YamlRepresenter();
		try {
			Field representers = BaseRepresenter.class.getDeclaredField("representers");
			Field multiRepresenters = BaseRepresenter.class.getDeclaredField("multiRepresenters");
			representers.setAccessible(true);multiRepresenters.setAccessible(true);
			Map<Class<?>, ?> r = (Map<Class<?>, ?>)representers.get(yamlp);
			Representers.addAll(r.keySet());
			Map<Class<?>, ?> r2 = (Map<Class<?>, ?>)multiRepresenters.get(yamlp);
			MultiRepresenters.addAll(r2.keySet());
			loadSuccess = true;
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
	
	String root = "";
	ErrorHandler err_handler = null;
	
	Map<Class<?>, Map<String, Object>> DescTree = new LinkedHashMap<>();
	Set<Class<?>> isUnDescSet = new HashSet<>();
	Set<Class<?>> isUnVerSet = new HashSet<>();

	Map<Class<?>,String> paths = new HashMap<Class<?>, String>();
	Map<Class<?>,Object> caches = new HashMap<Class<?>, Object>();
	
	/**创建一个配置管理器,参数:根目录*/
	public BukYmlConfig(String root) {
		this.root=root;
	}
	public BukYmlConfig(File root) {
		this.root=root.getAbsolutePath();
	}
	
	@Override
	public void load(Class<?> type) throws Exception{
		String path = paths.get(type);
		File file = new File(root,path.endsWith("/")?path:(path+".yml"));
		if(file.isDirectory()) {
			Map<String,Object> fm = new HashMap<String,Object>();
			Map<String,Object> dmm = new HashMap<>();
			for(File f:file.listFiles()) {
				String fname = RainStr.end(f.getName(),".yml");
				if(fname==null)continue;
				YamlConfiguration yml = new YamlConfiguration();
				try {
					yml.load(file);
				}catch (Exception e) {
					handleError(type,e);
					throw e;
				}

				String filever = yml.getString("config_version");
				int re = VersionUtil.compareToVer(VersionUtil.getVersion(type),filever);
				if(Math.abs(re)!=1||!isVerCheck(type)) {
					Object obj = deserialize(yml, type);
					if(isDesc(type)) {
						try {
							dmm.put(fname,BukYmlNode.loadDescTree(file));
						} catch (IOException e) {
							handleError(type,e);
						}
					}
					fm.put(fname,obj);
				}else {
					VersionException e = new VersionException(VersionUtil.getVersion(type), filever);
					handleError(type,e);throw e;
				}
			}
			if(isDesc(type))DescTree.put(type,dmm);
			caches.put(type,fm);
		}else if(RainStr.end(file.getName(),".yml")!=null){
			YamlConfiguration yml = new YamlConfiguration();
			try {
				yml.load(file);
			}catch (Exception e) {
				handleError(type,e);throw e;
			}
			String filever = yml.getString("config_version");
			int re = VersionUtil.compareToVer(VersionUtil.getVersion(type),filever);
			if(Math.abs(re)!=1||!isVerCheck(type)) {
				Object obj = deserialize(yml, type);
				if(isDesc(type)) {
					try {
						DescTree.put(type,BukYmlNode.loadDescTree(file));
					} catch (IOException e) {
						handleError(type,e);throw e;
					}
				}
				caches.put(type,obj);
			}else {
				VersionException e = new VersionException(VersionUtil.getVersion(type), filever);
				handleError(type,e);throw e;
			}
		}
	}

	@Override
	public void loadOrNew(Class<?> type) throws Exception{
		ErrorHandler oh = err_handler;
		try {
			err_handler = (a,b)->{};
			load(type);
		}catch (Exception e){
			if(e instanceof VersionException)
				throw e;
			err_handler = oh;
			try {
				caches.put(type,type.newInstance());
			} catch (InstantiationException|IllegalAccessException e1) {
				handleError(type, e1);throw e1;
			}
			try {
				save(type);
			} catch (Exception e1) {
				caches.remove(type);throw e1;
			}
		}finally {
			err_handler = oh;
		}
	}
	
	@Override
	public void setRootPath(String root) {
		this.root = root;
	}
	@Override
	public RainConfig add(Class<?> type, String path) {
		paths.put(type, path);
		return this;
	}
	@Override
	public void setPaths(Map<Class<?>,String> paths) {
		this.paths = paths;
	}
	@Override
	public Map<Class<?>,String> getPaths() {
		return paths;
	}
	
	@Override
	public void save(Class<?> type) throws IOException {
		Object obj = caches.get(type);
		boolean isDesc = isDesc(type);
		if(obj instanceof Map) {
			Map<String, Object> m = (Map<String,Object>)obj;
			Map<String, Object> dmm = (Map<String,Object>)DescTree.get(type);
			for(Entry<String, Object> en:m.entrySet()) {
				try {
					boolean isOverDesc = false;//是否采用默认配置进行覆盖
					Map<String, Object> dm = null;
					if(isDesc) {
						if(dmm!=null) {
							dm = (Map<String, Object>) dmm.get(en.getKey());
							if(dm==null)isOverDesc = true;
						}else isOverDesc = true;
					}
					YamlConfiguration yml = (YamlConfiguration) serialize(en.getValue(),isOverDesc);
					File f = new File(root+"/"+paths.get(type),en.getKey()+".yml");
					if(isDesc) {
						if(isOverDesc) {
							String str = BukYmlNode.confWithDescTreeToString(yml);
							BukYmlNode.saveStrToFile(str, f);
						}else BukYmlNode.saveConfWithDesc(dm,yml,f);
					}else
						yml.save(f);
				} catch (IOException e) {
					handleError(type, e);throw e;
				}
			}
			File fl = new File(root+"/"+paths.get(type));//删除多余的文件
			for(File f:fl.listFiles()) {
				if(!m.containsKey(RainStr.end(f.getName(),".yml")))
					f.delete();
			}
		}else {
			try {
				boolean isOverDesc = false;//是否采用默认配置进行覆盖
				Map<String, Object> dm = DescTree.get(type);
				if(isDesc&&dm==null) isOverDesc = true;
				
				YamlConfiguration yml = (YamlConfiguration) serialize(obj,isOverDesc);
				String ver = VersionUtil.getVersion(type);
				if(ver!=null)yml.set("config_version",ver);//覆盖版本
				
				File f = new File(root,paths.get(type)+".yml");
				if(isDesc) {
					if(isOverDesc) {
						String str = BukYmlNode.confWithDescTreeToString(yml);
						BukYmlNode.saveStrToFile(str, f);
					}else BukYmlNode.saveConfWithDesc(dm,yml,f);
				}else
					yml.save(f);
			} catch (IOException e) {
				handleError(type, e);throw e;
			}
		}
	}
	
	@Override
	public void loadAll() throws Exception {
		for(Class<?> type:paths.keySet()) {
			load(type);
		}
	}

	@Override
	public void loadAllOrNew() throws Exception {
		for(Class<?> type:paths.keySet()) {
			loadOrNew(type);
		}
	}

	@Override
	public void saveInFolder(Class<?> type, String name) throws IOException {
		Map<String, Object> m = (Map<String,Object>)caches.get(type);
		Object obj = m.get(name);
		File file = new File(root+"/"+paths.get(type),name+".yml");
		if(obj==null) {
			file.delete();
			return;
		}
		Map<String, Object> dmm = (Map<String,Object>)DescTree.get(type);
		boolean isDesc = isDesc(type);
		boolean isOverDesc = false;//是否采用默认配置进行覆盖
		Map<String, Object> dm = null;
		if(isDesc) {
			if(dmm!=null) {
				dm = (Map<String, Object>) dmm.get(name);
				if(dm==null)isOverDesc = true;
			}else isOverDesc = true;
		}
		
		YamlConfiguration yml = (YamlConfiguration) serialize(obj,isOverDesc);
		String ver = VersionUtil.getVersion(type);
		if(ver!=null)yml.set("config_version",ver);//覆盖版本
		try {
			if(isDesc) {
				if(isOverDesc) {
					String str = BukYmlNode.confWithDescTreeToString(yml);
					BukYmlNode.saveStrToFile(str, file);
				}else BukYmlNode.saveConfWithDesc(dm,yml,file);
			}else
				yml.save(file);
		} catch (IOException e) {
			handleError(type, e);throw e;
		}
	}

	@Override
	public void saveAll() throws IOException {
		for(Class<?> type:paths.keySet()) {
			save(type);
		}
	}

	@Override
	public <T> Map<String, T> getFolder(Class<T> type) {
		return (Map<String, T>) get(type);
	}
	
	@Override
	public <T> T get(Class<T> type) {
		T obj = (T) caches.get(type);
		return obj;
	}
	@Override
	public void set(Class<?> type,Object obj) {
		caches.put(type,obj);
	}
	
	@Override
	public void setErrorHandler(ErrorHandler handler) {
		this.err_handler = handler;
	}
	
	public void handleError(Class<?> type, Exception e) {
		if(err_handler!=null) {
			err_handler.handleError(paths.get(type), e);
		}else e.printStackTrace();
	}
	
	@Override
	public Map<String,Object> getDescTree(Class<?> c) {
		return DescTree.get(c);
	}
	@Override
	public void setDescTree(Class<?> c, Map<String,Object> tree) {
		DescTree.put(c,tree);
	}

	@Override
	public void setDesc(Class<?> c,boolean b) {
		if(!b)isUnDescSet.add(c);
		else isUnDescSet.remove(c);
	}
	@Override
	public boolean isDesc(Class<?> c) {
		return !isUnDescSet.contains(c);
	}
	@Override
	public void setVerCheck(Class<?> c,boolean b) {
		if(!b)isUnVerSet.add(c);
		else isUnVerSet.remove(c);
	}
	@Override
	public boolean isVerCheck(Class<?> c) {
		return !isUnVerSet.contains(c);
	}


	/**转自定义类对象为Configuration
	 * @param obj 对象
	 * @param isDesc 是否同时加入默认注释*/
	public static ConfigurationSection serialize(Object obj,boolean isDesc) {
		YamlConfiguration yml = new YamlConfiguration();
		Class<?> type = obj.getClass();
		String ver = VersionUtil.getVersion(type);
		yml.set("config_version",ver);//覆盖版本

		List<Field> fs = RainF.filter(type.getDeclaredFields(),(o)->
			!o.isAnnotationPresent(IgnoreConf.class)&&!Modifier.isStatic(o.getModifiers()));
		try {
			for(Field f:fs) {
				f.setAccessible(true);
				Object val = f.get(obj);
				val = serializeObj(val,isDesc);
				if(isDesc) {
					Desc desc = f.getAnnotation(Desc.class);
					if(desc!=null) {
						List<String> list = new ArrayList<>();
						for(String str:desc.value().split("\n"))list.add("#"+str);
						yml.set(f.getName()+"_desc",list);
					}
				}
				yml.set(f.getName(),val);
			}
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return yml;
	}
	
	/**Configuration转回对象*/
	public static <T> T deserialize(ConfigurationSection args,Class<T> target) {
		List<Field> fs = RainF.filter(target.getDeclaredFields(),(o)->
			!o.isAnnotationPresent(IgnoreConf.class)&&!Modifier.isStatic(o.getModifiers()));
		try {
			Object tobj = target.newInstance();
			for(Field f:fs) {
				Object v = args.get(f.getName());
				Type pt = f.getGenericType();if(!(pt instanceof ParameterizedType))pt=null;
				Class<?> type = f.isAnnotationPresent(SubClass.class)?f.getAnnotation(SubClass.class).value():f.getType();
				if(type.equals(Map.class))type = LinkedHashMap.class;
				if(type.equals(List.class))type = ArrayList.class;
				v = deserializeObj(v,type,(ParameterizedType)pt);
				f.setAccessible(true);
				f.set(tobj,v);
			}
			return (T)tobj;
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**判断是否需要转为Configuration对象,自动转换返回
	 * @param val 对象
	 * @param isDesc 是否同时加入默认注释*/
	public static Object serializeObj(Object val,boolean isDesc) {
		if(val==null||isBasicClass(val.getClass())) {//基础String类型 not do
		}else if(val instanceof Collection) {
			Collection<?> c = (Collection<?>)val;
			ArrayList<Object> cal = new ArrayList<>();
			for(Object co:c) cal.add(serializeObj(co,isDesc));
			val = cal;
		}else if(val instanceof Map) {
			Map<?, ?> m = (Map<?, ?>)val;
			LinkedHashMap<Object,Object> mal = new LinkedHashMap<>();
			for(Entry<?, ?> en:m.entrySet()) {
				mal.put(en.getKey(),serializeObj(en.getValue(),isDesc));
			}
			val = mal;
		}else if(val instanceof Array) {
			val = new ArrayList<>(Arrays.asList(val));
			val = serializeObj(val,isDesc);
			val = ((Collection) val).toArray();
		}else {
			boolean ok = false;
			for(Class<?> in:val.getClass().getInterfaces()) 
			if(MultiRepresenters.contains(in)) {
				ok=true;break;
			}
			if(!ok)val = serialize(val,isDesc);//对于自定义类,转为Configuration
		}
		return val;
	}
	
	/**判断对象是否为Configuration,是否要转回target对象,并自动返回转换结果*/
	public static Object deserializeObj(Object val,Class<?> type,ParameterizedType pt) {
		Class<?>[] interf = type.getInterfaces();
		if(val instanceof ConfigurationSection&&!type.equals(val.getClass())) {
			if(type.equals(Map.class)||ArrayUtils.contains(interf,Map.class)) {
				Type[] pts = pt.getActualTypeArguments();
				Class<?> objc = null;ParameterizedType objt = null;
				if(pts[1] instanceof ParameterizedType) {
					objc = (Class<?>) ((ParameterizedType)pts[1]).getRawType();
					objt = (ParameterizedType) pts[1];
				}else if(pts[1] instanceof Class)
					objc = (Class<?>) pts[1];
				else return null;//不支持>?

				if(objc.equals(Map.class))objc = LinkedHashMap.class;
				if(objc.equals(List.class))objc = ArrayList.class;
				
				ConfigurationSection con = ((ConfigurationSection)val);
				Map<Object,Object> cal = null;
				try {
					cal = (Map<Object,Object>)type.newInstance();
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
				if(cal!=null) {
					for(String key:con.getKeys(false)) {
						cal.put(key,deserializeObj(con.get(key),objc,objt));
					}
					val = cal;
				}else val = null;
			}else
			return deserialize((ConfigurationSection)val,type);
		}
		if(val==null||isBasicClass(type)) {//基础类型 not do
		}else if(type.equals(Collection.class)||ArrayUtils.contains(interf,Collection.class)) {
			Type[] pts = pt.getActualTypeArguments();
			Class<?> objc = null;ParameterizedType objt = null;
			if(pts[0] instanceof ParameterizedType) {
				objc = (Class<?>) ((ParameterizedType)pts[0]).getRawType();
				objt = (ParameterizedType) pts[0];
			}else if(pts[0] instanceof Class)
				objc = (Class<?>) pts[0];
			else return null; //不支持其他泛型
			if(objc.equals(Map.class))objc = LinkedHashMap.class;
			if(objc.equals(List.class))objc = ArrayList.class;

			Collection<?> c = (Collection<?>)val;
			Collection<Object> cal = null;
			try {
				cal = (Collection<Object>)type.newInstance();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
			if(cal!=null) {
				for(Object co:c) cal.add(deserializeObj(co,objc,objt));
				val = cal;
			}else val = null;
		}else if(type.isArray()) {
			int leng = Array.getLength(val);
			Object aal = Array.newInstance(type,leng);
			for(int i=0;i<leng;i++) {
				Object tobj = Array.get(val,i);
				Array.set(aal,i,deserializeObj(tobj,tobj.getClass(),null));
			}
			val = aal;
		}
		return val;
	}
	
	public static boolean isBasicClass(Class<?> c) {
		return c.equals(String.class)
				||c.isPrimitive()
				||Representers.contains(c)
				||Number.class.equals(c.getSuperclass())
				||Enum.class.equals(c);
	}
	
}
